#ifndef __LZJLEX_CPP__
#define __LZJLEX_CPP__

#include "utils.cpp"
#include "lexer_types.cpp"
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <deque>

using std::cin;
using std::cout;
using std::deque;
using std::endl;
using std::stringstream;
using std::vector;

// #define DEBUG
#ifdef DEBUG
#define DB(a, b) cout << a << " " << b << endl;
#else
#define DB(a, b) ;
#endif

// 用于将控制台输出流tee到文件中，但如果目标输出也是控制台，就不tee
#define REDIRECT(cout, sout, content)\
    cout << content;         \
    if (&cout != &sout)        \
        sout << content;

static Position curPos;

// 程序入口，返回token列表
deque<Token> lzjlex(std::istream &, std::ostream &);

// 在一行内处理
static bool analyzeLine(const string &, std::ostream &, deque<Token> &);

// 判断遇最后的换行是否合法（有没有把未完成的单词截断）
static bool isLegalLinebreak(TokenStateType);

// 正确完成一个单词
static void onDone(Token &, std::ostream &, deque<Token> &);

// 出错
static void onError(Token&, std::ostream&, string);

deque<Token> lzjlex(std::istream &sin, std::ostream &sout) {
    curPos.line = 0;
    string lineStr;
    deque<Token> tokenList;
    while (std::getline(sin, lineStr)) {
        DB("Getline>", lineStr);
        curPos.line++;
        if(!analyzeLine(lineStr, sout, tokenList)){
            cout << "Lexer terminated normally due to an error in input.";
            exit(0);
        }
    }
    tokenList.push_back(Token("$", TokenType::ENDFILE));
    return tokenList;
}

static bool analyzeLine(const string& line, std::ostream &sout, deque<Token>& tokenList){
    char ch;
    curPos.col = 0;
    Token result;
    TokenStateType state = TokenStateType::START;
    bool fake = false;
    for (; curPos.col < line.length(); ++curPos.col) {
        ch = line.at(curPos.col);
        fake = false;
        DB("  >", ch);
        if (state != TokenStateType::DONE && state != TokenStateType::ERROR) {
            result.word += ch;
            switch (state) {
            case TokenStateType::START:
                result.startPos = curPos;
                if (isOneWordOperator(ch)) { // 单目运算符直接Done
                    result.tokenType = getOperatorType(ch);
                    fake = true;
                    state = TokenStateType::DONE;
                } else if(ch == '<') {
                    result.tokenType = TokenType::LSHIFT;
                    state = TokenStateType::LT;
                } else if (ch == '>') {
                    result.tokenType = TokenType::RSHIFT;
                    state = TokenStateType::GT;
                } else if (isalpha(ch)) {
                    result.tokenType = TokenType::ID;
                    state = TokenStateType::ID_DIGIT;
                } else if (ch == '0') {
                    result.tokenType = TokenType::INTEGER;
                    state = TokenStateType::DIG_ZERO;
                } else if ('1' <= ch && ch <= '9') {
                    result.tokenType = TokenType::INTEGER;
                    state = TokenStateType::DIG_DIGIT;
                } else if (ch == '\"') {
                    result.tokenType = TokenType::STRING;
                    state = TokenStateType::STR_CHAR;
                } else if (ch == ' ') {
                    state = TokenStateType::START;
                    result.word.pop_back();
                } else {
                    state = TokenStateType::ERROR;
                }
                break;
            case TokenStateType::ID_LETTER: // 这两个状态是类似的
            case TokenStateType::ID_DIGIT:  // 这两个状态是类似的
                if (isalpha(ch)) {
                    state = TokenStateType::ID_LETTER;
                } else if(ch == '_'){
                    state = TokenStateType::ID_UNDERLINE;
                } else if(isdigit(ch)){
                    state = TokenStateType::ID_DIGIT;
                } else{
                    state = TokenStateType::DONE;
                }
                break;
            case TokenStateType::ID_UNDERLINE:
                if(isalpha(ch)){
                    state = TokenStateType::ID_LETTER;
                } else if(isdigit(ch)){
                    state = TokenStateType::ID_DIGIT;
                } else{
                    state = TokenStateType::ERROR;
                }
                break;
            case TokenStateType::DIG_ZERO:
                if(ch == 'l' || ch == 'L'){
                    fake = true;
                } 
                state = TokenStateType::DONE;
                break;
            case TokenStateType::DIG_DIGIT:
                if(isdigit(ch)){
                    // same state
                } else if(ch == 'l' || ch == 'L'){
                    fake = true;
                    state = TokenStateType::DONE;
                } else {
                    state = TokenStateType::DONE;
                }
                break;
            case TokenStateType::STR_CHAR:
                if(ch == '\\'){
                    result.word.pop_back();
                    state = TokenStateType::STR_ESCAPE;
                } else if(ch == '\"'){
                    fake = true;
                    state = TokenStateType::DONE;
                } else {
                    // same state
                }
                break;
            case TokenStateType::STR_ESCAPE:
                if(isEscape(ch)){
                    result.word.pop_back();
                    result.word.append(toEscape(ch));
                    state = TokenStateType::STR_CHAR;
                } else {
                    state = TokenStateType::ERROR;
                }
                break;
            case TokenStateType::LT:
                if(ch == '<'){
                    fake = true;
                    state = TokenStateType::DONE;
                } else {
                    state = TokenStateType::ERROR;
                }
                break;
            case TokenStateType::GT:
                if (ch == '>') {
                    fake = true;
                    state = TokenStateType::DONE;
                } else {
                    state = TokenStateType::ERROR;
                }
                break;
            default: // shall never reach
                cout << "Miss a case！！！\n";
                return false;
            }
        } 
        if (state == TokenStateType::DONE){
            if(!fake){
                curPos.col--;
                result.word.pop_back();
            }
            state = TokenStateType::START;
            onDone(result, sout, tokenList);
        } else if(state == TokenStateType::ERROR){
            onError(result, sout, line);
            return false;
        }
    } // end input while loop
    if(isLegalLinebreak(state)){
        if(!fake && state != TokenStateType::START){ // 没有fake
            onDone(result, sout, tokenList);
        }
    } else {
        onError(result, sout, line);
        cout << "[Error] Line "<<curPos.line << ": Unexpected line break" << endl;
        return false;
    }
    sout << endl;
    return true;
}

static void onError(Token& result, std::ostream& sout, string line) {
    string prefix = "[Error] Line " + std::to_string(curPos.line) + "(" + std::to_string(curPos.col) + "): ";
    REDIRECT(cout, sout,
             endl << prefix << "Word (" << result.word << ") is an illegal "
                  << getTokenLiteral(result.tokenType) << endl;);
    string prespace = " ";
    for (int i = 0; i < prefix.length() - 1; ++i){
        prespace += " ";
    }
    REDIRECT(cout, sout, prespace << line << endl << prespace);
    for (int i = 0; i < curPos.col - result.word.length();++i){
        REDIRECT(cout, sout, " ");
    }
    for (int i = 0; i < result.word.length() - 1; ++i) {
        REDIRECT(cout, sout, "~");
    }
    REDIRECT(cout, sout, "^\n\n");
}

static void onDone(Token &result, std::ostream &sout, deque<Token> &tokenList) {
    if (result.tokenType == TokenType::ID) { // 判断是否是关键字
        result.tokenType = getKeywordType(result.word);
    }
    sout << getTokenLiteral(result.tokenType);
    if (result.tokenType == TokenType::INTEGER || result.tokenType == TokenType::STRING ||
        result.tokenType == TokenType::ID) {
        sout << "(" << result.word << ")";
    }
    result.endPos = curPos;
    tokenList.push_back(result);
    result.word.clear();
    sout << "  ";
}

static bool isLegalLinebreak(TokenStateType state){
    return state == TokenStateType::START 
            || state == TokenStateType::DONE
            || state == TokenStateType::ID_LETTER
            || state == TokenStateType::ID_DIGIT
            || state == TokenStateType::DIG_ZERO
            || state == TokenStateType::DIG_DIGIT;
}

#endif